<?php
namespace lib\wechat;

/**
 * @Auth: JH <hu@lunaz.cn>
 * Class Base
 * @package lib\wechat
 */
class Base
{

    const API_DEFAULT = 'https://api.weixin.qq.com/cgi-bin';
    const API_BASE = 'https://api.weixin.qq.com';
    protected static $accessToken;
    protected static $config;
    protected static $debug = false;
    private static $file_cache = false;
    private static $form = array();
    private static $curl = null;

    function __construct()
    {
        self::$debug = isset( self::$config['debug'] ) ? self::$config['debug'] : false;
        self::$file_cache = isset( self::$config['file_cache'] ) ? self::$config['file_cache'] : false;
    }

    protected function getToken()
    {
        if (!self::$accessToken) {
            $file_cache = __DIR__ . '/cache/access_token.php';
            if ($file_cache && is_file( $file_cache )) {
                $cache = require $file_cache;
                if ($cache['access_token'] && $cache['time'] > time()) {
                    return self::$accessToken = $cache['access_token'];
                }
            }

            if (empty( self::$config )) {
                throw new Exception( '请提供微信公众接口凭据配置' );
            }
            $api = self::makeUrl(
                '/token',
                array(
                    'grant_type' => 'client_credential',
                    'appid' => self::$config['appId'],
                    'secret' => self::$config['appSecret']
                )
            );
            $result = self::http( $api );
            if (isset( $result['access_token'] )) {
                if (self::$file_cache) {
                    self::setCache( $result['access_token'], time() + ( $result['expires_in'] - 100 ) );
                }
                return self::$accessToken = $result['access_token'];
            } else {
                throw new Exception( '获取TOKEN失败' );
            }
        }
        return self::$accessToken;
    }

    protected function getAccessToken()
    {
        return array(
            'access_token' => $this->getToken()
        );
    }

    private static function setCache( $token, $time )
    {
        $file_cache = __DIR__ . '/cache/access_token.php';
        if (!file_exists($file_cache)) {
            mkdir(dirname($file_cache), 0755);
        }
        if (is_file($file_cache) && !is_writable( $file_cache )) {
            chmod( $file_cache, 0666 );
        }
        $data = <<<cache
<?php
return array(
            'access_token' => '%s',
            'time' => %u
        );
cache;
        file_put_contents( $file_cache, sprintf( $data, $token, $time ) );
    }

    /**
     * 生成XML
     * @param array $array
     * @param string $root
     * @return string
     */
    protected function makeXML( array $array, $root = 'xml' )
    {
        $xml = '';
        $cdata = function ( $str ) {
            return sprintf( '<![CDATA[%s]]>', preg_replace( "/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/", '', $str ) );
        };

        foreach ($array as $key => $value) {
            if (is_array( $value )) {
                $data = $this->makeXML( $value, false );
            } elseif (!is_int( $value )) {
                $data = $cdata( $value );
            } else {
                $data = $value;
            }
            $xml .= sprintf( "<%s>%s</%s>", $key, $data, $key );
        }

        return $root === false ? $xml : sprintf( "<%s>%s</%s>", $root, $xml, $root );
    }


    /**
     * 解析XML为数组
     * @param string $xml
     * @return array
     * @throws Exception
     */
    protected static function parseXML( $xml )
    {
        if (stripos( $xml, '<xml>' ) === false) {
            throw new Exception( 'XML数据不合法' );
        }
        return (array)simplexml_load_string( $xml, 'SimpleXMLElement', LIBXML_NOCDATA );
    }

    /**
     * 生成微信API——Url
     * @param $path
     * @param array $params
     * @param string $host
     * @return string
     */
    protected static function makeUrl( $path, array $params, $host = '' )
    {
        $link = stripos( $host, '?' ) ? '&' : '?';
        if ('' === $host) {
            $host = self::API_DEFAULT;
        }
        return $host . $path . $link . http_build_query( $params );
    }

    protected static function http( $url, $post_params = '', $retry = 3 )
    {
        $post_data = $post_params ? self::postFields( $post_params ) : null;
        !self::$curl && self::$curl = curl_init();
        curl_setopt_array(
            self::$curl,
            array(
                CURLOPT_DNS_CACHE_TIMEOUT => 3600,
                CURLOPT_SSL_VERIFYHOST => false,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_HEADER => false,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_URL => $url,
                CURLOPT_CUSTOMREQUEST => $post_params ? 'POST' : 'GET',
                CURLOPT_TIMEOUT => 60,
                CURLOPT_POST => $post_params !== '',
                CURLOPT_POSTFIELDS => $post_data
            )
        );

        $result = curl_exec( self::$curl );
        if (curl_errno( self::$curl )) {
            if ($retry > 0) {
                return self::http( $url, $post_params, --$retry );
            }
            throw new Exception( 'Curl error: ' . curl_error( self::$curl ) );
        } else {
            $status = curl_getinfo( self::$curl );
            if ((int)$status['http_code'] !== 200) {
                return self::http( $url, $post_params, --$retry );
            }
            $result = self::parseJson( $result, stripos( $status['content_type'], 'json' ) === false );
            if (isset( $result['errcode'] ) && !self::$debug) {
                $diff = array_diff_key( $result, array('errcode' => 1, 'errmsg' => 2) );
                if (empty( $diff )) {
                    if ($result['errcode'] == 0) {
                        return true;
                    }
                    if ($result['errcode'] == 40001 && self::$file_cache) {
                        self::setCache( '', 0 );
                    }
                    self::errLog( $result );
                    return false;
                }
                return $diff;
            }
            return $result;
        }
    }

    static function parseJson( $data, $check = true )
    {
        $result = json_decode( $data, true );
        if ($check) {
            if (json_last_error() === JSON_ERROR_NONE) {
                return $result;
            }
            return $data;
        }
        return $result;
    }

    protected function addMultiFormData( array $form )
    {
        self::$form += $form;
    }

    private static function postFields( $data )
    {
        if (is_array( $data )) {
            return self::makeJson( $data );
        } elseif ($data) {
            return self::makeFile( $data );
        } else {
            return null;
        }
    }

    private static function makeFile( $file )
    {
        if (!is_file( $file )) {
            throw new Exception( "没找到文件：{$file}" );
        }
        !self::$curl && self::$curl = curl_init();
        if (function_exists( 'curl_file_create' )) {
            curl_setopt( self::$curl, CURLOPT_SAFE_UPLOAD, true );
            $form = array(
                'media' => curl_file_create( $file )
            );
        } else {
            version_compare( PHP_VERSION, '5.5', '>' ) ||
            curl_setopt( self::$curl, CURLOPT_SAFE_UPLOAD, false );
            $file_info = self::getFileInfo( $file );
            $info = array(
                $file_info['basename'],
                self::fileMime( $file_info['extension'] )
            );
            $form = array(
                'media' => "@{$file};postname={$info[0]};mime={$info[1]}"
            );
        }
        if (self::$form) {
            $form += self::$form;
        }
        return $form;
    }

    protected static function getFileInfo( $file_path, $key = 'all' )
    {
        $info = pathinfo( $file_path );
        if ($key === 'all') {
            return $info;
        }
        if (isset( $info[$key] )) {
            return $info[$key];
        }
        return false;
    }

    private static function fileMime( $ext )
    {
        $mime = array(
            //applications
            'ai' => 'application/postscript',
            'eps' => 'application/postscript',
            'exe' => 'application/octet-stream',
            'doc' => 'application/vnd.ms-word',
            'xls' => 'application/vnd.ms-excel',
            'ppt' => 'application/vnd.ms-powerpoint',
            'pps' => 'application/vnd.ms-powerpoint',
            'pdf' => 'application/pdf',
            'xml' => 'application/xml',
            'odt' => 'application/vnd.oasis.opendocument.text',
            'swf' => 'application/x-shockwave-flash',
            // archives
            'gz' => 'application/x-gzip',
            'tgz' => 'application/x-gzip',
            'bz' => 'application/x-bzip2',
            'bz2' => 'application/x-bzip2',
            'tbz' => 'application/x-bzip2',
            'zip' => 'application/zip',
            'rar' => 'application/x-rar',
            'tar' => 'application/x-tar',
            '7z' => 'application/x-7z-compressed',
            // texts
            'txt' => 'text/plain',
            'php' => 'text/x-php',
            'html' => 'text/html',
            'htm' => 'text/html',
            'js' => 'text/javascript',
            'css' => 'text/css',
            'rtf' => 'text/rtf',
            'rtfd' => 'text/rtfd',
            'py' => 'text/x-python',
            'java' => 'text/x-java-source',
            'rb' => 'text/x-ruby',
            'sh' => 'text/x-shellscript',
            'pl' => 'text/x-perl',
            'sql' => 'text/x-sql',
            // images
            'bmp' => 'image/x-ms-bmp',
            'jpg' => 'image/jpeg',
            'jpeg' => 'image/jpeg',
            'gif' => 'image/gif',
            'png' => 'image/png',
            'tif' => 'image/tiff',
            'tiff' => 'image/tiff',
            'tga' => 'image/x-targa',
            'psd' => 'image/vnd.adobe.photoshop',
            //audio
            'mp3' => 'audio/mpeg',
            'mid' => 'audio/midi',
            'ogg' => 'audio/ogg',
            'mp4a' => 'audio/mp4',
            'wav' => 'audio/wav',
            'wma' => 'audio/x-ms-wma',
            // video
            'avi' => 'video/x-msvideo',
            'dv' => 'video/x-dv',
            'mp4' => 'video/mp4',
            'mpeg' => 'video/mpeg',
            'mpg' => 'video/mpeg',
            'mov' => 'video/quicktime',
            'wm' => 'video/x-ms-wmv',
            'flv' => 'video/x-flv',
            'mkv' => 'video/x-matroska'
        );
        if (isset( $mime[$ext] )) {
            return $mime[$ext];
        }
        return 'application/octet-stream';
    }


    /**
     * 编译JSON
     * @param array $array
     * @return string
     */
    protected static function makeJson( array $array )
    {
        return json_encode( $array, JSON_UNESCAPED_UNICODE );
    }

    private static function errLog( $data )
    {
        $uri = $_SERVER['REQUEST_URI'];
        is_array( $data ) && $data = json_encode( $data, JSON_UNESCAPED_UNICODE );
        $data = sprintf( "\n=============\n┏━ %s  %s\n┗━ %s", date( 'Y-m-d H:i:s' ), $uri, $data );
        file_put_contents( __DIR__ . '/log/err-' . date( 'Ymd' ) . '.log', $data, FILE_APPEND | LOCK_EX );
    }

    static function log( $data )
    {
        $uri = $_SERVER['REQUEST_URI'];
        is_array( $data ) && $data = json_encode( $data, JSON_UNESCAPED_UNICODE );
        $data = sprintf( "\n=============\n┏━ %s  %s\n┗━ %s", date( 'Y-m-d H:i:s' ), $uri, $data );
        file_put_contents( __DIR__ . '/log/log-' . date( 'Ymd' ) . '.log', $data, FILE_APPEND | LOCK_EX );
    }

    function __destruct()
    {
        // TODO: Implement __destruct() method.
        if (is_resource( self::$curl )) {
            curl_close( self::$curl );
        }

    }

}